package portfolio;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.io.*;
import java.util.Vector;
import org.jaxen.JaxenException;
import org.jaxen.XPath;
import org.jaxen.jdom.JDOMXPath;
import org.jdom.*;

import languages.xml.*;

/*
 * This class is used for saving the output of the planners on training problems.
 * It is also used for running the configuration of the portfolios.
 * 	Plans is structured in 2 level. The outer list represents the planners, the inner the training instances
 *	e.g. Plans.at(1).at(2) indicates the plan generated by the 2nd planner for the 3rd problem.
 * 
 *  Best_quality and Best_CPU_time are used for saving, for each problem, the best known plan and the
 *  shortest CPU time.  
 */

public class GeneratedPlans {

	private Vector<Vector<Element>> Plans;
	
	//The following 2 lists are used for storing, for each training problem, the best solution and/or 
	//the minimum CPU time across all the considered planners.
	private Vector<Double> Best_quality;
	private Vector<Double> Best_CPU_time;
	
	String allocation; // the allocation strategy
	String selection; //the selection strategy
	String target; //the target to optimize
	
	public String getTarget() {
		return target;
	}

	public void setTarget(String target) {
		this.target = target;
	}

	public GeneratedPlans(){
		Plans = new Vector<Vector<Element>>();
		Plans.add(new Vector<Element>());
		Best_quality = new Vector<Double>();
		Best_CPU_time = new Vector<Double>();
	}
	
	public String getAllocation() {
		return allocation;
	}

	public void setAllocation(String allocation) {
		this.allocation = allocation;
	}

	public String getSelection() {
		return selection;
	}

	public void setSelection(String selection) {
		this.selection = selection;
	}
	
	//save the output of a planner on a given problem
	public void save_plan(int planner, int problem, Element xmlPlan){
		if(Plans.size() == planner)
			Plans.add(new Vector<Element>());
		Plans.elementAt(planner).add(xmlPlan);
		return;
	}
	
	public Element get_plan(int planner, int problem){
		return Plans.get(planner).get(problem);
	}
	
	public void print_all_plans(){
		for(int i=0; i < Plans.size(); i++)
			for(int j=0; j < Plans.get(i).size(); j++){
				XMLUtilities.printXML(Plans.get(i).get(j));
			}
	}
	
	
	/*
	 * Given the min, max number of planners per portfolio and the timeout, this function check
	 * the allocation / selection techniques and the target to optimise for running the correct
	 * configuration function
	 */
	public ConfiguredPortfolio portfolio_configuration(int min, int max, float timeout){
		ConfiguredPortfolio to_configure = new ConfiguredPortfolio(max);
//		for(int i=0; i< consideredplannerslist.size()-1; i++){
		//			for(int j=0; j < training_instances.size(); j++ ){
		//				Element test = plans.get_plan(i, j);
		//				System.out.println(test.getChild("statistics").getChildText("time"));
		//			}
		//		}
		String technique=allocation+selection+target;
		System.out.println(technique);
		boolean did_something=false;
		if(technique.contains("uniformIPC-score")){
			if(technique.contains("quality")){
				to_configure = IPC_same(min,max,timeout,1);
				did_something=true;
			}
			else
				if(technique.contains("runtime")){
					to_configure = IPC_same(min,max,timeout,0);
					did_something=true;
				}
		}
		if(technique.contains("heuristic")){
			if(technique.contains("quality")){
				to_configure = heuristic(min,max,timeout,1);
				did_something=true;
			}
		}
		if(!did_something){
			System.out.println("Allocation and/or Selection strategy not supported (yet..)");
		}
		return to_configure;
	}
	
	/*
	 * This function is used for configuring a portfolio used the IPC_score as selecting technique 
	 * and by allocating the same CPU_time to all the included planners as allocation strategy.
	 * It generates all the possible portfolios by calling the GeneratePortfolios(min,max);
	 * and then simulate the run of each portfolio on training problems. It returns the best one.
	 */
	
	private ConfiguredPortfolio IPC_same(int min, int max, float timeout, int target){
		//TARGET 1 MEANS QUALITY, TARGET 0 MEANS RUNTIME
		Vector<ConfiguredPortfolio> portfolios; 
		int position_selected=-1;
		portfolios = GeneratePortfolios(min,max);
		for(int i=0; i < portfolios.size(); i++){
			//THIS IS THE CYCLE FOR ALLOTTING TIME TO THE PLANNERS IN THE PORTFOLIOS
			Vector<Vector<Float>> CPU_t = new Vector<Vector<Float>> ();
			int size=portfolios.elementAt(i).numberPlanners();
			float allotted=timeout/size;
			for(int h=0; h < size; h++){
				CPU_t.add(new Vector<Float> ());
				CPU_t.elementAt(h).add(allotted);
			}
			portfolios.elementAt(i).setCPU_time(CPU_t);
		}
		if(target==1)
			calculateBestQuality();
		else
			calculateBestCPU();
		for (int j=0; j < portfolios.size(); j++){
			if(target==1)
				SimulateSequentialExecutionQ(portfolios.elementAt(j));
			else
				SimulateSequentialExecutionR(portfolios.elementAt(j));
		}	
		double max_score=0;
		System.out.println("I configured and tested the following portfolios.");
		for(int i=0; i < portfolios.size(); i++ ){
			System.out.println("["+ i + "] " + portfolios.elementAt(i).toString());
			if(portfolios.elementAt(i).getScore() > max_score){
				max_score=portfolios.elementAt(i).getScore();
				position_selected=i;
			}
		}
		System.out.println(">> I selected portfolio ["+position_selected+"] (in case of same score, I prefer the portfolio composed by less planners ;) )");
		return portfolios.elementAt(position_selected);
	}

	/*
	 * This function simulates the execution of a portfolio, given in input, for maximising the 
	 * IPC quality score.
	 */
	private void SimulateSequentialExecutionQ(ConfiguredPortfolio to_simulate){
		double quality=java.lang.Double.MAX_VALUE;
		for(int i=0; i < Plans.get(0).size(); i++ ){
			for(int j=0; j < to_simulate.numberPlanners(); j++){
				if (Plans.get(to_simulate.getPlanner(j) ).get(i).getChild("plan").getChildren().size() >= 1){
					if(Float.parseFloat(Plans.get(to_simulate.getPlanner(j) ).get(i).getChild("statistics").getChildText("toolTime")) <= to_simulate.getCPU_time_specific(j).elementAt(0))
						if(quality > Plans.get(to_simulate.getPlanner(j) ).get(i).getChild("plan").getChildren().size())
							quality= Plans.get(to_simulate.getPlanner(j) ).get(i).getChild("plan").getChildren().size();
				}
			}
			if(quality != java.lang.Double.MAX_VALUE)
				to_simulate.addScore( this.Best_quality.elementAt(i) / quality );
		}
		return;
	}
	
	/*
	 * This function simulates the execution of a portfolio, given in input, for maximising the 
	 * IPC speed score.
	 */
	
	private void SimulateSequentialExecutionR(ConfiguredPortfolio to_simulate){
		double runtime=java.lang.Double.MAX_VALUE;
		double offset=0; //this is needed for counting the planners that failed before...
		for(int i=0; i < Plans.get(0).size(); i++ ){
			for(int j=0; j < to_simulate.numberPlanners(); j++){
				if (Plans.get(to_simulate.getPlanner(j) ).get(i).getChild("plan").getChildren().size() >= 1){
					if(Float.parseFloat(Plans.get(to_simulate.getPlanner(j) ).get(i).getChild("statistics").getChildText("toolTime")) <= to_simulate.getCPU_time_specific(j).elementAt(0)){
						runtime = Float.parseFloat(Plans.get(to_simulate.getPlanner(j) ).get(i).getChild("statistics").getChildText("toolTime")) + offset;
						break;
					}
				}
				offset += to_simulate.getCPU_time_specific(j).elementAt(0);
			}
			if(runtime != java.lang.Double.MAX_VALUE)
				to_simulate.addScore( this.Best_CPU_time.elementAt(i) / runtime );
		}
		return;
	}

	
	//this function fills the best_quality list by evaluating, for each training problem, the best
	//solution found by considered planners.
	private void calculateBestQuality(){
		double min;
		for(int i=0; i < Plans.get(0).size(); i++ ){
			min=-1;
			for(int j=0; j < Plans.size(); j++ ){
				if (Plans.get(j).get(i).getChild("plan").getChildren().size() >= 1){
					if(min==-1)
						min=Plans.get(j).get(i).getChild("plan").getChildren().size();
					else
						if(min > Plans.get(j).get(i).getChild("plan").getChildren().size())
							min=Plans.get(j).get(i).getChild("plan").getChildren().size();
				}
			}
			Best_quality.add(min);
		}
		return;
	}

	//this function fills the best_CPU list by evaluating, for each training problem, the best
	//runtime found by considered planners.
	private void calculateBestCPU(){ 
		double min;
		for(int i=0; i < Plans.get(0).size(); i++ ){
			min=-1;
			for(int j=0; j < Plans.size(); j++ ){
				if (Plans.get(j).get(i).getChild("plan").getChildren().size() >= 1){
					if(min==-1)
						min=Float.parseFloat(Plans.get(j).get(i).getChild("statistics").getChildText("toolTime"));
					else
						if(min > Float.parseFloat(Plans.get(j).get(i).getChild("statistics").getChildText("toolTime")))
							min=Float.parseFloat(Plans.get(j).get(i).getChild("statistics").getChildText("toolTime"));
				}
			}
			Best_CPU_time.add(min);
		}
		return;
	}
	
	
	//function used for generating *all* the possible portfolios given the min and max number of planner to include
	// the output is a list of portfolios that have set only the included planners.
	private Vector<ConfiguredPortfolio> GeneratePortfolios(int min, int max){
		Vector<ConfiguredPortfolio> to_return = new Vector<ConfiguredPortfolio> ();
		int number_of_planners = Plans.size();
		if(max > number_of_planners)
			max=number_of_planners;
		Vector<Vector<String>> prova = MathHelp.Permutations(number_of_planners, max);
		for(int j=min-1; j < prova.size(); j++){
			for(int h=0; h< prova.elementAt(j).size(); h++){
				ConfiguredPortfolio insert = new ConfiguredPortfolio(max);
				//System.out.println("J: "+j+" H: "+h+" "+prova.elementAt(j).elementAt(h));
				//CONFIGURE THE PORTFOLIO
				String[] varie = prova.elementAt(j).elementAt(h).split(",");
				for(int w=0; w < varie.length; w++)
					insert.addPlanner(Integer.parseInt(varie[w]));
				to_return.add(insert);
			}
		}
		return to_return;
	}
	
//function that exploits a heuristic approach for configuring the most promising sequential portfolio
// target: 1-> quality, 0-> runtime, 2-> coverage
	private ConfiguredPortfolio heuristic(int min, int max, float timeout, int target){
		ConfiguredPortfolio to_return = new ConfiguredPortfolio(max);
		double current_h_value = 0;
		double score;
		int was_in;
		Vector<Float> help;
		double max_improvement; 
		int i_max_improvement;
		float granularity = timeout/10;
		
		if (target==1)
			calculateBestQuality();
		
		for (float ciclo = granularity; ciclo <= timeout; ciclo += granularity){
			  max_improvement=0;
			  i_max_improvement=-1;
			  for (int j=0; j < Plans.size(); j++){
				  to_return.setScore(0.0);
				  if( !to_return.presentPlanner(j)){
					  if(to_return.numberPlanners() > max)
						  continue;
					  //add the planner to the portfolio with the granularity as CPU time
					  help = new Vector<Float>();
					  help.add(granularity);
					  to_return.addPlanner(j, help);
					  was_in=0;
				  }
				  else{
					  help=to_return.getCPU_time_specific(j);
					  help.set(0, help.elementAt(0)+granularity);
					  to_return.setCPU_time_specific(j, help);
					  //update the CPU time given to the planner
					  was_in=1;
				  }
				  if (target==1)
					  SimulateSequentialExecutionQ(to_return);
				  score=to_return.getScore();
				  if( score - current_h_value > max_improvement){
					  max_improvement=score - current_h_value;
					  i_max_improvement=j;
				  }
				  //remove added planner
				  if(was_in == 0)
					  to_return.removeLastPlanner();
				  else{
					  help=to_return.getCPU_time_specific(j);
					  help.set(0, help.elementAt(0)-granularity);
					  to_return.setCPU_time_specific(j, help);
				  }  
			  }
			  if(max_improvement==0)
				  break;
			  current_h_value += max_improvement;
			  if(!to_return.presentPlanner(i_max_improvement)){
				  help = new Vector<Float>();
				  help.add(granularity);
				  to_return.addPlanner(i_max_improvement, help);
			  }else{
				  help=to_return.getCPU_time_specific(i_max_improvement);
				  help.set(0, help.elementAt(0)+granularity);
				  to_return.setCPU_time_specific(i_max_improvement, help);
			  }
			  if(to_return.total_CPU_time() + granularity > timeout)
				  break;
		}
		
		return to_return;
	}
	
	
}
